package ace.module.stock.core.impl.dao.aggregate.impl;

import ace.cmp.core.constants.CoreConstant;
import ace.cmp.util.AceAssertUtils;
import ace.cmp.util.TreeUtils;
import ace.module.stock.api.enums.WmsStockObjectTypeEnum;
import ace.module.stock.api.enums.WmsStockRecordActionTypeEnum;
import ace.module.stock.api.enums.WmsStockRecordReverseFlagEnum;
import ace.module.stock.core.impl.dao.aggregate.WmsStockAggregate;
import ace.module.stock.core.impl.dao.aggregate.helper.WmsStockRecordAggregateHelper;
import ace.module.stock.core.impl.dao.entity.QWmsStock;
import ace.module.stock.core.impl.dao.entity.QWmsStockRecord;
import ace.module.stock.core.impl.dao.entity.WmsStock;
import ace.module.stock.core.impl.dao.entity.WmsStockBatchRecord;
import ace.module.stock.core.impl.dao.entity.WmsStockRecord;
import ace.module.stock.core.impl.dao.repository.WmsStockBatchRecordRepository;
import ace.module.stock.core.impl.dao.repository.WmsStockRecordRepository;
import ace.module.stock.core.impl.dao.repository.WmsStockRepository;
import com.querydsl.core.types.Expression;
import com.querydsl.sql.SQLExpressions;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 库存 aggregate implement.
 *
 * @author Caspar
 */
@Slf4j
@AllArgsConstructor
@Component
public class WmsStockAggregateImpl implements WmsStockAggregate {

  private final WmsStockRepository wmsStockRepository;
  private final WmsStockBatchRecordRepository wmsStockBatchRecordRepository;
  private final WmsStockRecordRepository wmsStockRecordRepository;
  private final WmsStockRecordAggregateHelper wmsStockRecordAggregateHelper;

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public void addSkuStockAndAutoCalcRecursionParentStocks(List<WmsStock> skuStockAndAllRecursionParentStocks, WmsStockBatchRecord wmsStockBatchRecord, List<WmsStockRecord> skuWmsStockRecords) {
    AceAssertUtils.isFalse(
        this.wmsStockRecordAggregateHelper.hasOnlyAddActionType(skuWmsStockRecords),
        "仅支持增加操作"
    );
    this.wmsStockBatchRecordRepository.insertNotNullFieldAndInjectFrameworkField(wmsStockBatchRecord);
    this.wmsStockRecordRepository.insertNotNullFieldAndInjectFrameworkField(skuWmsStockRecords);
    this.modifySkuStock(skuWmsStockRecords);
    this.calcRecursionParentStocks(skuStockAndAllRecursionParentStocks);

  }

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public void subtractSkuStockAndCalcRecursionParentStocks(List<WmsStock> skuStockAndAllRecursionParentStocks, WmsStockBatchRecord wmsStockBatchRecord, List<WmsStockRecord> skuWmsStockRecords) {
    AceAssertUtils.isFalse(
        this.wmsStockRecordAggregateHelper.hasOnlySubtractActionType(skuWmsStockRecords),
        "仅支持扣减操作"
    );
    this.wmsStockBatchRecordRepository.insertNotNullFieldAndInjectFrameworkField(wmsStockBatchRecord);
    this.wmsStockRecordRepository.insertNotNullFieldAndInjectFrameworkField(skuWmsStockRecords);
    this.modifySkuStock(skuWmsStockRecords);
    this.calcRecursionParentStocks(skuStockAndAllRecursionParentStocks);
  }

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public void addOrSubtractSkuStockAndCalcRecursionParentStocks(List<WmsStock> skuStockAndAllRecursionParentStocks, WmsStockBatchRecord wmsStockBatchRecord,
      List<WmsStockRecord> skuWmsStockRecords, WmsStockRecordActionTypeEnum actionTypeEnum) {
    if (WmsStockRecordActionTypeEnum.ADD.equals(actionTypeEnum)) {
      this.addSkuStockAndAutoCalcRecursionParentStocks(skuStockAndAllRecursionParentStocks, wmsStockBatchRecord, skuWmsStockRecords);
    } else if (WmsStockRecordActionTypeEnum.SUBTRACT.equals(actionTypeEnum)) {
      this.subtractSkuStockAndCalcRecursionParentStocks(skuStockAndAllRecursionParentStocks, wmsStockBatchRecord, skuWmsStockRecords);
    } else {
      throw new IllegalArgumentException("仅支持增加或扣减操作");
    }
  }

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public void modifySkuStockAndCalcRecursionParentStocks(List<WmsStock> skuStockAndAllRecursionParentStocks, WmsStockBatchRecord wmsStockBatchRecord, List<WmsStockRecord> skuWmsStockRecords) {
    this.wmsStockBatchRecordRepository.insertNotNullFieldAndInjectFrameworkField(wmsStockBatchRecord);
    this.wmsStockRecordRepository.insertNotNullFieldAndInjectFrameworkField(skuWmsStockRecords);
    this.modifySkuStock(skuWmsStockRecords);
    this.calcRecursionParentStocks(skuStockAndAllRecursionParentStocks);
  }

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public void reverseAndCalcRecursionParentStocks(
      List<WmsStock> skuStockAndAllRecursionParentStocks,
      WmsStockBatchRecord wmsStockBatchRecord,
      List<WmsStockRecord> reverseWmsStockRecords) {
    AceAssertUtils.isFalse(
        this.wmsStockRecordAggregateHelper.hasOnlyAddOrSubtractActionType(reverseWmsStockRecords),
        "仅支持增加与扣减操作"
    );
    this.wmsStockBatchRecordRepository.insertNotNullFieldAndInjectFrameworkField(wmsStockBatchRecord);
    this.wmsStockRecordRepository.insertNotNullFieldAndInjectFrameworkField(reverseWmsStockRecords);
    this.reverseWmsStockRecord(reverseWmsStockRecords);
    this.modifySkuStock(reverseWmsStockRecords);
    this.calcRecursionParentStocks(skuStockAndAllRecursionParentStocks);
  }

  /**
   * 更新需要还原的库存操作记录.
   *
   * @param wmsStockRecords 主动还原操作记录
   */
  private long reverseWmsStockRecord(List<WmsStockRecord> wmsStockRecords) {
    long rowCount = this.wmsStockRecordRepository.update(sqlUpdateClause -> {
      wmsStockRecords.forEach(wmsStockRecord -> {
        QWmsStockRecord qWmsStockRecord = QWmsStockRecord.wmsStockRecord;
        sqlUpdateClause
            .set(qWmsStockRecord.reverseFlag, WmsStockRecordReverseFlagEnum.REVERSE.getCode())
            .set(qWmsStockRecord.reverseId, wmsStockRecord.getId())
            .set(qWmsStockRecord.updateTime, System.currentTimeMillis())
            .set(qWmsStockRecord.rowVersion, qWmsStockRecord.rowVersion.add(1))
            .where(qWmsStockRecord.id.eq(wmsStockRecord.getReverseId()))
            .where(qWmsStockRecord.reverseFlag.eq(WmsStockRecordReverseFlagEnum.NONE.getCode()))
            .addBatch();
      });

      return sqlUpdateClause.execute();
    });

    return rowCount;
  }

  /**
   * 递归更新上级库存.
   *
   * @param skuStockAndAllRecursionParentStocks 所有sku库存操作记录涉及的库存记录,包含sku库存与所有上级库存
   */
  private long calcRecursionParentStocks(List<WmsStock> skuStockAndAllRecursionParentStocks) {
    return this.wmsStockRepository.update(sqlUpdateClause -> {
      // 从下至上遍历库存记录进行库存更新
      TreeUtils.traversalBottomToUp(skuStockAndAllRecursionParentStocks,
          // 判断是否最底层库存记录
          skuWmsStock -> WmsStockObjectTypeEnum.PRODUCT_SKU.getCode().equals(skuWmsStock.getObjectType()),
          WmsStock::getId,
          WmsStock::getParentId,
          // 遍历操作生成sql语句
          (entry) -> {
            // 层数,0=最底层
            long level = entry.getKey();
            if (level > 0) {
              WmsStock parentWmsStock = entry.getValue();
              Long parentId = parentWmsStock.getId();
              QWmsStock qWmsStock = QWmsStock.wmsStock;

              Expression calcChildrenStockQuantityExpression = SQLExpressions
                  .select(SQLExpressions.sum(qWmsStock.quantity))
                  .from(qWmsStock)
                  .where(qWmsStock.parentId.eq(parentId))
                  .where(qWmsStock.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT));

              sqlUpdateClause.set(qWmsStock.quantity, calcChildrenStockQuantityExpression)
                  .set(qWmsStock.updateTime, System.currentTimeMillis())
                  .set(qWmsStock.rowVersion, qWmsStock.rowVersion.add(1))
                  .where(qWmsStock.id.eq(parentId))
                  .addBatch();
            }
          }
      );
      return sqlUpdateClause.execute();
    });
  }

  /**
   * 根据库存操作类型,进行库存数量增加、扣减或设置.
   *
   * @param skuWmsStockRecords 根据库存操作类型
   */
  private long modifySkuStock(List<WmsStockRecord> skuWmsStockRecords) {
    List<WmsStockRecord> addWmsStockRecords = skuWmsStockRecords.stream()
        .filter(p -> WmsStockRecordActionTypeEnum.ADD.getCode().equals(p.getActionType()))
        .collect(Collectors.toList());

    List<WmsStockRecord> subtractWmsStockRecords = skuWmsStockRecords.stream()
        .filter(p -> WmsStockRecordActionTypeEnum.SUBTRACT.getCode().equals(p.getActionType()))
        .collect(Collectors.toList());

    List<WmsStockRecord> setWmsStockRecords = skuWmsStockRecords.stream()
        .filter(p -> WmsStockRecordActionTypeEnum.SET.getCode().equals(p.getActionType()))
        .collect(Collectors.toList());

    long expectAllUpdateCount = addWmsStockRecords.size() + subtractWmsStockRecords.size() + setWmsStockRecords.size();

    if (expectAllUpdateCount == 0) {
      return 0;
    }

    long rowCount = this.wmsStockRepository.update(sqlUpdateClause -> {
      QWmsStock qWmsStock = QWmsStock.wmsStock;
      addWmsStockRecords.forEach(wmsStockRecord -> {
        sqlUpdateClause.set(qWmsStock.quantity, qWmsStock.quantity.add(wmsStockRecord.getQuantity()))
            .set(qWmsStock.updateTime, System.currentTimeMillis())
            .set(qWmsStock.rowVersion, qWmsStock.rowVersion.add(1))
            .where(qWmsStock.id.eq(wmsStockRecord.getId()))
            .addBatch();
      });

      subtractWmsStockRecords.forEach(wmsStockRecord -> {
        sqlUpdateClause.set(qWmsStock.quantity, qWmsStock.quantity.subtract(wmsStockRecord.getQuantity()))
            .set(qWmsStock.updateTime, System.currentTimeMillis())
            .set(qWmsStock.rowVersion, qWmsStock.rowVersion.add(1))
            .where(qWmsStock.id.eq(wmsStockRecord.getId()))
            .addBatch();
      });

      setWmsStockRecords.forEach(wmsStockRecord -> {
        sqlUpdateClause.set(qWmsStock.quantity, wmsStockRecord.getQuantity())
            .set(qWmsStock.updateTime, System.currentTimeMillis())
            .set(qWmsStock.rowVersion, qWmsStock.rowVersion.add(1))
            .where(qWmsStock.id.eq(wmsStockRecord.getId()))
            .addBatch();
      });

      return sqlUpdateClause.execute();
    });
    return rowCount;
  }
}